import scipy
import copy
from helper_codes.general_stimulus import general_stimulus, GeneralStim

def moving_dot_settings():
    '''
    This function tells you what settings are needed and in what order for this stimulus.
    Inputs:
        None
    Returns:
        settings        : A list of tupples.  In each tupple is
                            (setting_name, setting_description, default_value).
                          The list is of length num_settings + 1, so that the final
                             value in the list is True/False wether or not the stimulus
                             can be triggered via the parallel port or not.
    '''
    settings = []
    settings.append( ('dot_size',
                     'The size of the moving dot. (normalized RF units)',
                     1.0 ))
    settings.append( ('duration',
                     'The amount of time the stimulus will move. (seconds)',
                     3.5 ))
    settings.append( ('wait_time',
                     'The amount of time after stimulus movement. (seconds)',
                     1.0 ))
    settings.append( ('start_pos',
                     'Where the stimulus will first appear. (normalized RF units) [list]',
                     [-0.5,-0.5] ))
    settings.append( ('end_pos',
                     'Where the stimulus will move towards and end up. (normalized RF units) [list]',
                     [-1.0, 1.0] ))
    settings.append( ('shade',
                     '1.0 for black dots, -1.0 for white dots.',
                     1.0 ))
    settings.append( ('mirror_pos',
                     'The position of a "mirrored" stimulus relative to the RF. (normalized RF units) [list]',
                     [0.0,0.0] ))
    settings.append( ('delay',
                     'Time between showing the mirror and the regular stimulus (seconds) negative values supported.',
                     0.0 ))
    settings.append( ('win_size',
                     'The dimensions of the stimulus delivery window. (pixels) [list]',
                     [1440,900] ))
    settings.append( ('refresh_rate',
                     'The number of screen refreshes per second. (Hz)',
                     60 ))
    settings.append(True) # this stimulus can be triggered
    return settings

def moving_dot_runtime(dot_size, duration, wait_time, start_pos, end_pos, shade, 
               mirror_pos, delay, win_size, refresh_rate):
    """
    Calculate and return the number of seconds that this stimulus will need to run, after being triggered.
    """
    return duration + wait_time + abs(delay) + 0.99 # 0.99 added so rounding is always up.

class Stim(GeneralStim):
    """
    This is a basic stimulus object, it must have:
        .setup(window) method to set itself up to be drawn
        .draw() method to draw itself to the display
        .evolve() method to set itself up for the next draw
        .exhausted variable set to True after stim is done.
    """
    def __init__( self, dot_size, duration, wait_time, start_pos, end_pos, shade, 
                  mirror_pos, delay, refresh_rate, preview, save_frames):
        self.dot_size = dot_size
        self.duration = duration
        self.wait_time = wait_time
        self.start_pos = scipy.array(start_pos)
        self.end_pos = scipy.array(end_pos)
        self.shade = shade
        self.mirror_pos = scipy.array(mirror_pos)
        self.delay = delay
        self.refresh_rate = refresh_rate
        self.preview = preview
        self.save_frames = save_frames

        self.exhausted = False
        self.times_evolved = 0

    def setup(self, window):
        from psychopy import visual

        # how many frames we'll display
        num_frames = int(self.duration*self.refresh_rate)
        wait_frames  = int(self.wait_time*self.refresh_rate)
        delay_frames = int(abs(self.delay)*self.refresh_rate)
        # how the dot should move each frame
        dx         = (self.end_pos[0]-self.start_pos[0])/(num_frames*1.0)
        dy         = (self.end_pos[1]-self.start_pos[1])/(num_frames*1.0)
        positions = [[self.start_pos[0]+dx*i, self.start_pos[1]+dy*i] 
                      for i in xrange(num_frames)]
        last_position = self.end_pos
        for i in xrange(wait_frames):
            positions.append( last_position )
        self.positions = scipy.array(positions)
        self.num_frames = num_frames
        self.delay_frames = delay_frames

        # make the textures of the dots.
        texture = scipy.ones((4,4),dtype=scipy.float64)*-1.0*self.shade
        self.dot = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(self.dot_size, self.dot_size), 
                                        mask='circle',
                                        units='norm')
        self.mirror_dot = visual.PatchStim( window, 
                                        tex=texture, 
                                        pos=self.positions[0],
                                        size=(self.dot_size, self.dot_size), 
                                        mask='circle',
                                        units='norm')
        self.textures = [self.dot, self.mirror_dot]


    def draw(self):
        self.dot.draw()
        self.mirror_dot.draw()

    def evolve(self):
        self.times_evolved += 1

        if self.delay < 0.0: # mirror_dot goes first
            mirror_dot_pos_i = min(len(self.positions)-1, self.times_evolved)
            dot_pos_i = max(0, self.times_evolved-self.delay_frames)
        else: # dot goes first, then mirror
            mirror_dot_pos_i = max(0, self.times_evolved-self.delay_frames)
            dot_pos_i = min(len(self.positions)-1, self.times_evolved)
        self.dot.setPos(self.positions[dot_pos_i])
        self.mirror_dot.setPos(self.positions[mirror_dot_pos_i]+self.mirror_pos)

        if self.times_evolved >= len(self.positions)-1 + self.delay_frames:
            if (not self.preview) or self.save_frames:
                self.exhausted = True
            else:
                self.times_evolved = 0 # continue looping forever in preview mode.

def moving_dot(dot_size, duration, wait_time, start_pos, end_pos, shade, 
               mirror_pos, delay, win_size, refresh_rate,
                     window=None,
                     eye_cup=None, 
                     rf=None, 
                     save_frames=False,
                     save_frames_factor=1.0,
                     save_frames_size=100,
                     display_saved_frames=False,
                     preview=True):
    """
    This stimulus shows a single black dot on a white background
    Inputs:
        dot_size        : the size of the DIAMETER of the moving dot in normalized units.
        duration        : the time where the bar will be moving
        wait_time       : the time after movement, before screen returns to previous state.
        mirror_pos      : the position of a "mirror" of the regular stimulus in normalized units.
        delay           : delay between regular and mirror display, negative values supported.
        win_size        : dimensions of the screen in pixels, (1440,900) is default
        refresh_rate    : refresh rate in Hz of the video, 60.0 is default.
        preview         : If false the program will wait for a pulse trigger, otherwise it won't.
        save_frames_factor  : if None, then no frames are saved, otherwise it is a 
                                scaling factor so we can scale down the frames and
                                output a frames.gif (an animated gif file).
        eye_cup         : an Eye_cup object, see its docstring for more info.
        rf              : RF object, to set up new coordinate system.
    Returns:
        times     : The times when frames were shown, with zero being the first frame time.
    """
    stim_list = [ Stim( dot_size, duration, wait_time, start_pos, end_pos, shade, 
                        mirror_pos, delay, refresh_rate, preview, save_frames) ]
    return_values = general_stimulus( stim_list, shade, win_size, 
                                         window=window,
                                         eye_cup=eye_cup, 
                                         rf=rf, 
                                         save_frames=save_frames,
                                         save_frames_factor=save_frames_factor,
                                         save_frames_size=save_frames_size,
                                         display_saved_frames=display_saved_frames,
                                         preview=preview)
    return return_values

if __name__ == '__main__':
   
    # ---------------------------------
    # -- Set the stimulus parameters --
    # ---------------------------------
    dot_size  = 0.1      # diameter of dot in rf units
    duration  = 10.0      # in seconds
    wait_time = 5.0
    start_pos = (0.0, 0.0) # in rf units
    end_pos   = (-0.5,-0.5) # in rf units
    shade      = 1.0                            # 1.0 for white, -1.0 for black background
    mirror_pos = [-0.5,0.5]
    delay      = -2.0

    # display parameters
    win_size = (1440,900)  # in pixels
    refresh_rate = 60.0    # in Hz

    return_values = moving_dot(dot_size, duration, wait_time, start_pos, 
               end_pos, shade, mirror_pos, delay, win_size, refresh_rate,
                save_frames = True,
                display_saved_frames = True)

 
